/*
 * Copyright (c) 2017-2021, Lindenis Tech. Ltd.
 * All rights reserved.
 *
 * File:
 *
 * Description:
 *
 * Author:
 *      xiaoshujun@lindeni.com
 *
 * Create Date:
 *      2021/03/12
 *
 * History:
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <assert.h>

#include "osal_mutex.h"
#include "osal_cond.h"
#include "osal_common.h"

#include "lindnet.h"

#include "osal_log.h"

typedef struct _sample_lindnet_ctx_t
{
    lindnet_t *             p_net;
    int                     sockfd;
    OSAL_mutex *            mutex;
    OSAL_cond *             cond;
} sample_lindnet_ctx_t;

#define TYPE_SERVER 0
#define TYPE_CLIENT 1

static int sample_lindnet_on_event(void * user, lindnet_msg_e msg, void * ext1, void * ext2)
{
    sample_lindnet_ctx_t * p_ctx = (sample_lindnet_ctx_t *)user;
    int ret = 0;
    int index = -1;

    switch (msg)
    {
        case LINDNET_MSG_SESSION_CONNECT:
            OSAL_LockMutex(p_ctx->mutex);
            p_ctx->sockfd = *(int*)ext1;
            OSAL_CondSignal(p_ctx->cond);
            OSAL_UnlockMutex(p_ctx->mutex);
            break;
        case LINDNET_MSG_SESSION_DISCONNECT:
            logd("client %d disconneted", *(int*)ext1);
            OSAL_LockMutex(p_ctx->mutex);
            p_ctx->sockfd = -1;
            OSAL_UnlockMutex(p_ctx->mutex);
            break;
        default:
            logw("unknown msg: %d", msg);
            break;
    }

    return 0;
}

static void _lindnet_print_usage(char * this)
{
    printf("\033[32m");
    printf("Usage: \n");
    printf("    %s -s/-c -i [ip] -p [port] -udp/-tcp/-rdt.\n", this);
    printf("        -s          : run as a server \n");
    printf("        -c          : run as a client \n");
    printf("        -i [ip]     : the server ip \n");
    printf("        -p [port]   : the server port \n");
    printf("        -udp        : use udp protocol \n");
    printf("        -tcp        : use tcp protocol \n");
    printf("        -rdt        : use rdt protocol \n");
    printf("    such as: %s -s -i 192.168.1.2 -p 9980 -rdt \n", this);
    printf("           : %s -c -i 192.168.1.2 -p 9980 -rdt \n\n", this);
    printf("First launch a server, then run a client to connect the server.\n");
    printf("Then you can send msgs from client to the server.\n");
    printf("\033[0m\n");
}

int main(int argc, char *argv[])
{
    int ret = 0;
    int i = 0;
    sample_lindnet_ctx_t context = {0};
    lindnet_config_t config = {0};
    int port = 0;
    char * ip = "0.0.0.0";
    int type = TYPE_SERVER;   // server
    lindnet_type_e protocol = LINDNET_TYPE_RDT;
    char * net_inf = NULL;
    char buffer_send[1024] = {0};
    char buffer_recv[1024] = {0};
    int len = 0;

    if ((argc == 2)
        && (!strcmp(argv[1], "-h")))
    {
        _lindnet_print_usage(argv[0]);
        return 0;
    }

    for (i = 1; i < argc; )
    {
        if (!strcmp(argv[i], "-i"))
        {
            ip = argv[i+1];
            i += 2;
        }
        else if (!strcmp(argv[i], "-p"))
        {
            port = atoi(argv[i+1]);
            i += 2;
        }
        else if (!strcmp(argv[i], "-s"))
        {
            type = TYPE_SERVER;
            i += 1;
        }
        else if (!strcmp(argv[i], "-c"))
        {
            type = TYPE_CLIENT;
            i += 1;
        }
        else if (!strcmp(argv[i], "-udp"))
        {
            protocol = LINDNET_TYPE_UDP;
            i += 1;
        }
        else if (!strcmp(argv[i], "-tcp"))
        {
            protocol = LINDNET_TYPE_TCP;
            i += 1;
        }
        else if (!strcmp(argv[i], "-rdt"))
        {
            protocol = LINDNET_TYPE_RDT;
            i += 1;
        }
        else
        {
            i++;
        }
    }

    context.mutex = OSAL_CreateMutex();
    context.cond = OSAL_CreateCond();

    config.conv = (uint32_t)0x12345678;
    config.mtu = 1400;
    config.sock_sndbuf_size = 128*1024;
    config.sock_rcvbuf_size = 128*1024;
    config.rcv_timeout_ms = 2000;
    config.block = 1;
    config.user = &context;
    config.on_event = sample_lindnet_on_event;
    context.p_net = lindnet_create(protocol, &config);
    if (!context.p_net)
    {
        loge("lindnet_create failed");
        return -1;
    }

    if (type == TYPE_CLIENT)
    {
        // client
        ret = context.p_net->connect(context.p_net, ip, port, 5000);
        if (ret < 0)
        {
            loge("lindnet_connect failed");
            goto _exit_;
        }
    }
    else
    {
        // server
        ret = context.p_net->bind(context.p_net, ip, port);
        if (ret < 0)
        {
            loge("lindnet_bind failed");
            goto _exit_;
        }
    }

    if (protocol == LINDNET_TYPE_TCP
        && type == TYPE_SERVER)
    {
        // wait tcp client
        OSAL_LockMutex(context.mutex);
        ret = OSAL_CondWait(context.cond, context.mutex);
        OSAL_UnlockMutex(context.mutex);
        if (ret != 0)
        {
            loge("wait tcp client connect failed");
            goto _exit_;
        }
    }
    else
    {
        context.sockfd = context.p_net->get_sockfd(context.p_net);
    }
    char sock_ip[16] = {0};
    uint16_t sock_port = 0;
    context.p_net->get_sockaddr(context.p_net, sock_ip, &sock_port);
    logd("sockfd: %d, addr: %s:%d", context.sockfd, sock_ip, sock_port);

    while(1)
    {
        if (type == TYPE_CLIENT)
        {
            printf("Input msg(Q/q to quit): ");
            memset(buffer_send, 0, sizeof(buffer_send));
            fgets(buffer_send, 1024, stdin);

            if (!strcmp(buffer_send, "q\n") || !strcmp(buffer_send, "Q\n"))
            {
                break;
            }

            context.p_net->send(context.p_net, context.sockfd, buffer_send, strlen(buffer_send));
            int64_t last_time_ms = get_tick_ms();
            do {
                len = context.p_net->recv(context.p_net, context.sockfd, buffer_recv, 1023);
                if (len > 0) {
                    buffer_recv[len] = 0;
                    printf("messge from server: %s", buffer_recv);
                    break;
                }
                if (get_tick_ms() - last_time_ms > 1000)
                {
                    last_time_ms = get_tick_ms();
                    printf("messge timeout\n");
                    break;
                }
                usleep(1000);
            }while(1);
        }
        else
        {
            memset(buffer_recv, 0, sizeof(buffer_recv));
            len = context.p_net->recv(context.p_net, context.sockfd, buffer_recv, 1023);
            if (len > 0) {
                printf("messge from client: %s", buffer_recv);
                context.p_net->send(context.p_net, context.sockfd, buffer_recv, len);
            } else {
                usleep(5000);
            }
        }
    }

_exit_:
    context.p_net->close(context.p_net);

    OSAL_DestroyCond(context.cond);
    OSAL_DestroyMutex(context.mutex);

    return 0;
}

